#include <cstdlib>
#include <iostream>
#include <netdb.h>
#include <unistd.h>

#include "posix_threads.hpp"
#include "posix_threads_thread.hpp"
#include "posix_io_sockets.hpp"
#include "posix_threads_mutex.hpp"
#include "posix_threads_condition.hpp"
#include "posix_threads_criticalsection.hpp"
#include "posix_time.hpp"

#include <queue>

static int threadFunc( int * param )
{
	return 2*(*param);
}

static void c_func_create( )
{
	std::cout << "posixcpp c_func_create" << std::endl;
	int a = 314;
	posix::threads::Thread th1( (posix::threads::Thread::ThreadFunction_t)threadFunc,
						 (void *)&a );
	try
	{
		th1.start();
		a = (int)th1.join();
	}
	catch (std::exception & ex)
	{
		std::cout << "%TEST_FAILED% time=0 testname=c_func_create (posixcpp) "\
				"message=" << ex.what() << std::endl;
		return;
	}
	if (a != 628)
		std::cout << "%TEST_FAILED% time=0 testname=c_func_create (posixcpp) "\
			"message=thread function result is incorrect" << std::endl;
}

static void class_create()
{
	std::cout << "posixcpp class_create" << std::endl;
	
	class Thread_class : public posix::threads::Thread
	{
	protected:
		void run() override
		{
			this->setResult((void*)467);
		}
	};
	
	try
	{
		Thread_class th1;
		th1.start();
		if ( (int)th1.join() != 467 )
			std::cout << "%TEST_FAILED% time=0 testname=class_create (posixcpp) "\
				"message=thread result is incorrect" << std::endl;
	}
	catch (std::exception & ex)
	{
		std::cout << "%TEST_FAILED% time=0 testname=class_create (posixcpp) "\
				"message=" << ex.what() << std::endl;
	}
}

static void wrap_existing_thread( )
{
	std::cout << "posixcpp wrap_existing_thread" << std::endl;
	
	int a = 314;
	
	pthread_t tid;
	if ( pthread_create(&tid, NULL,
			(posix::threads::Thread::ThreadFunction_t)threadFunc,
			(void*)&a)!= 0)
	{
		std::cout << "%TEST_FAILED% time=0 wrap_existing_thread (posixcpp) "\
				"message=" << "pthread_create:" << errno << std::endl;
		return;
	}
	
	posix::threads::Thread th1(tid);
	try
	{
		a = (int)th1.join();
	}
	catch (std::exception & ex)
	{
		std::cout << "%TEST_FAILED% time=0 testname=wrap_existing_thread (posixcpp) "\
				"message=" << ex.what() << std::endl;
	}
	
	if (a != 628)
		std::cout << "%TEST_FAILED% time=0 testname=wrap_existing_thread (posixcpp) "\
			"message=thread function result is incorrect" << std::endl;
}

static void cancel_test()
{
	std::cout << "posixcpp cancel_test" << std::endl;
	
	class Thread_class : public posix::threads::Thread
	{
	protected:
		void run() override
		{
			while(true)
				testCancel();
		}
	};
	
	Thread_class th;
	
	try
	{
		th.start();
		sleep(1);
		th.cancel();
		th.join();
	}
	catch (std::exception & ex)
	{
		std::cout << "%TEST_FAILED% time=0 testname=cancel_test (posixcpp) "\
			"message=thread function result is incorrect" << std::endl;
	}
}

static void attributes_test()
{
	std::cout << "posixcpp attributes_test" << std::endl;
	
	posix::threads::Thread thisThread = posix::threads::Thread::self();
	
	std::cout << "Cancel state " << thisThread.cancelState() << std::endl;
	std::cout << "Cancel type " << thisThread.cancelType() << std::endl;
	std::cout << "Sched parameters " << thisThread.schedParam() << std::endl;
	
	posix::threads::Thread::Attributes attr;
	attr.setSchedPolicy( posix::threads::SchedulerPolicy_t::FIFO );
	attr.setSchedParam( 3 );
	attr.setInheritSched( posix::threads::SchedulerInheritance_t::EXPLICIT );
	
	class Thread1 : public posix::threads::Thread
	{
	protected:
		void run()
		{
			std::cout << posix::threads::Thread::self().schedParam() << std::endl;
		}
	};
	
	Thread1 thread;
	thread.setAttributes( &attr );
	thread.start();
	thread.join();
}

template <typename T>
class BoundedQueue {
public:
    BoundedQueue(size_t maxSize) : 
    m_maxSize(maxSize),
    m_empty(m_lock),
    m_full(m_lock) {

    }
    
    void enqueue(T item) {
        posix::threads::CriticalSection cs(this->m_lock);
        
        while (this->m_queue.size() >= this->m_maxSize)
            this->m_full.wait();
        
        this->m_queue.push(item);
        
        if (this->m_queue.size() == 1)
            this->m_empty.broadcast();
    }
    T dequeue() {
        posix::threads::CriticalSection cs(this->m_lock);
        
        while (this->m_queue.size() == 0)
            this->m_empty.wait();
        
        T result = this->m_queue.front();
        this->m_queue.pop();
        
        if (this->m_queue.size() == this->m_maxSize-1)
            this->m_full.broadcast();
        
        return result;
    }
    size_t size() {
        posix::threads::CriticalSection cs(this->m_lock);
        
        return this->m_queue.size();
    }
private:
    std::queue<T> m_queue;
    posix::threads::Mutex m_lock;
    posix::threads::Condition m_empty;
    posix::threads::Condition m_full;
    size_t m_maxSize;
};

BoundedQueue<int> queue(5);

void queues_test() {
    std::cout << "posixcpp queues_test" << std::endl;
    
    static int i = 0;
    
    class Producer : public posix::threads::Thread {
    protected:
        void run() {
            while(true) {
                queue.enqueue(i);
                std::cout << "Producer inserted element " << i++ << std::endl;
                posix::time::nanoSleep(500000000);
            }
        }
    };
    
    class Consumer : public posix::threads::Thread {
    protected:
        void run() {
            int j;
            while(true) {
                j = queue.dequeue();
                std::cout << "Consumer got element " << j << std::endl;
                posix::time::nanoSleep(500000000);
            }
        }
    };
    
    Producer prod;
    Consumer cons;
    prod.start();
    posix::time::nanoSleep(posix::time::Timespec(4,100000000));
    cons.start();
    posix::time::nanoSleep(posix::time::Timespec(4,100000000));
    prod.cancel();
    prod.join();
    posix::time::nanoSleep(posix::time::Timespec(4,500000000));
    cons.cancel();
    cons.join();
}

int main( int argc, char** argv )
{
	std::cout << "%SUITE_STARTING% posixcpp" << std::endl;
	std::cout << "%SUITE_STARTED%" << std::endl;

	std::cout << "%TEST_STARTED% c_func_create (posixcpp)" << std::endl;
	c_func_create();
	std::cout << "%TEST_FINISHED% time=0 c_func_create (posixcpp)" << std::endl;

	std::cout << "%TEST_STARTED% class_create (posixcpp)" << std::endl;
	class_create();
	std::cout << "%TEST_FINISHED% time=0 class_create (posixcpp)" << std::endl;
	
	std::cout << "%TEST_STARTED% wrap_existing_thread (posixcpp)" << std::endl;
	wrap_existing_thread();
	std::cout << "%TEST_FINISHED% time=0 wrap_existing_thread (posixcpp)" << std::endl;
	
	std::cout << "%TEST_STARTED% cancel_test (posixcpp)" << std::endl;
	cancel_test();
	std::cout << "%TEST_FINISHED% time=0 cancel_test (posixcpp)" << std::endl;

	std::cout << "%TEST_STARTED% attributes_test (posixcpp)" << std::endl;
	attributes_test();
	std::cout << "%TEST_FINISHED% time=0 attributes_test (posixcpp)" << std::endl;
	
    std::cout << "%TEST_STARTED% queues_test (posixcpp)" << std::endl;
	queues_test();
	std::cout << "%TEST_FINISHED% time=0 queues_test (posixcpp)" << std::endl;
    
	std::cout << "%SUITE_FINISHED% time=0" << std::endl;

	return (EXIT_SUCCESS);
}
